package com.chinasoftinc.devkit;

import com.alibaba.fastjson.JSON;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

/**
 * FA3.0模板配置
 *
 * @since 2022-05-16
 */
public class FAConfigUtil {
    public static void main(String[] args) {
        if (args == null || args.length == 0) {
            LogUtil.info("FATemplate自动化配置脚本");
            LogUtil.info("为避免脚本执行过程出现异常，请不要使用其他程序打开项目, 避免文件无法写入修改");

            FAConfigInfo appInfo = new FAConfigInfo();
            Scanner scanner = new Scanner(System.in);
            collectAppinfo(appInfo, scanner);
            start(appInfo);
        } else if ("ns".equals(args[0])) {
            NANFAConfigUtil.run();
        }
    }

    private static void collectAppinfo(FAConfigInfo appInfo, Scanner scanner) {
        LogUtil.info("当前项目是否蓝牙模式:(y/n)");
        String bleInput = scanner.next();
        Pattern yesNoRegex = Pattern.compile("[yn]", Pattern.CASE_INSENSITIVE);
        while (!yesNoRegex.matcher(bleInput).find()) {
            LogUtil.info("无效输入, 只接受Y/N");
            bleInput = scanner.next();
        }
        appInfo.setBle("Y".equalsIgnoreCase(bleInput));

        LogUtil.info("请输入完整的项目包名(bundleName), 支持2-15级目录, 示例 com.chinasoftinc.grow");
        String pkgRegex = "([a-z][\\w]+\\.){1,15}[a-z][\\w]+";
        String pkgInput = scanner.next();
        while (!pkgInput.matches(pkgRegex)) {
            LogUtil.info("无效的包名, 请重新输入 参考: com.chinasoftinc.grow");
            pkgInput = scanner.next();
        }
        appInfo.setPkgName(pkgInput);

        LogUtil.info("请输入AGC平台申请的OAuth 2.0客户端ID值, 示例: 105064701");
        String appIdRegex = "[0-9]{8,12}";
        String appId = scanner.next();
        while (!appId.matches(appIdRegex)) {
            LogUtil.info("无效的AppID, 请重新输入, 示例: 105064701");
            appId = scanner.next();
        }
        appInfo.setAppId(appId);

        // 产品名称
        LogUtil.info("请输入产品名称, 中英文用,分割, 示例: 小马台灯,XIAOMA Lamp");
        String inputName = scanner.nextLine();
        while (isEmptyOrNextLine(inputName)) {
            inputName = scanner.nextLine();
        }
        String names = inputName.replace("，", ",");
        String[] namesSplit = names.split(",");
        while (namesSplit.length != 2) {
            LogUtil.info("中英文产品名称输入无效, 请重新输入, 示例: 小马台灯,XIAOMA Lamp");
            inputName = scanner.next();
            names = inputName.replace("，", ",");
            namesSplit = names.split(",");
        }
        appInfo.setProductChName(namesSplit[0]);
        appInfo.setProductEnName(namesSplit[1]);

        // confirm
        String confirmInfo = String.format(
            "蓝牙模式: %s\n" +
                "包名: %s\n" +
                "AppId: %s\n" +
                "FA名称(en,zh): %s,%s\n" +
                "信息录入结束, 键入 'y' 继续, 键入 'r' 重新录入, 按 Ctrl + C 退出\n"
            , formatBoolean(appInfo.isBle()),
            appInfo.getPkgName(),
            appInfo.getAppId(),
            appInfo.getProductChName(),
            appInfo.getProductEnName()
        );
        LogUtil.info(confirmInfo);
        String continueInput = scanner.next();
        Pattern yrRegex = Pattern.compile("[yr]", Pattern.CASE_INSENSITIVE);
        while (!yrRegex.matcher(continueInput).find()) {
            LogUtil.info("无效输入, 只接受y/r");
            continueInput = scanner.next();
        }
        if ("r".equalsIgnoreCase(continueInput)) {
            appInfo = new FAConfigInfo();
            collectAppinfo(appInfo, scanner);
        } else {
            IOUtils.ioClose(scanner);
            start(appInfo);
        }
    }

    static boolean isEmptyOrNextLine(String value) {
        return value == null || value.length() == 0 || value.replace("\r", "").equals("\n");
    }

    static String formatBoolean(boolean value) {
        return value ? "是" : "否";
    }

    private static void start(FAConfigInfo appInfo) {
        try {
            String currentPath = System.getProperty("user.dir");
            // String currentPath = "C:\\dev\\project\\template\\test\\HilinkTemplateV3.0";
            appInfo.setRoot(currentPath);
            appInfo.parse();
            executeConfig(appInfo);
            removeJsPage(appInfo);
            LogUtil.info("配置执行结束, 更多配置可前往 " + findFile(appInfo.getJavaSrcRoot(), "FAConfig.java").getPath());
            removeGit(appInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void removeJsPage(FAConfigInfo appInfo) {
        File bleConnectPage = new File(appInfo.getRoot(), "\\entry\\src\\main\\js\\connection\\");
        File wifiConnectPage = new File(appInfo.getRoot(), "\\entry\\src\\main\\js\\default\\");
        if (appInfo.isBle()) {
            IOUtils.deleteFileOrDir(wifiConnectPage);
            LogUtil.info("移除wifi连接页面");
        } else {
            IOUtils.deleteFileOrDir(bleConnectPage);
            LogUtil.info("移除蓝牙连接页面");
        }
        String configInfo = "{\r\n" +
            "        \"name\": \"" + (appInfo.isBle ? "default" : "connection") + "\",\r\n" +
            "        \"pages\": [\r\n" +
            "          \"pages/index/index\",\r\n" +
            "          \"pages/privacy/privacy\"\r\n" +
            "        ],\r\n" +
            "        \"window\": {\r\n" +
            "          \"designWidth\": 720,\r\n" +
            "          \"autoDesignWidth\": true\r\n" +
            "        }\r\n" +
            "      },";
        replyFileContext(appInfo.getConfigJsonFile(), configInfo, "");
    }

    private static void removeGit(FAConfigInfo appInfo) {
        if (appInfo.isFirstConfig) {
            LogUtil.info("检测到是第一次配置项目, 将移除git目录");
            IOUtils.deleteFileOrDir(new File(appInfo.getRoot(), ".git"));
        }
    }

    public static void throwError(String err) {
        String msg = err + ", 请手动修改或联系模板维护人员";
        System.err.println(msg);
        throw new RuntimeException(msg);
    }

    private static void executeConfig(FAConfigInfo appInfo) throws IOException {
        // 是否蓝牙模式
        replyFileContext(
            appInfo.getFAConfigJavaFile(),
            "public boolean isBleMode() {\r\n" +
                "        return " + !appInfo.isBle() + ";\r\n" +
                "    }",
            "public boolean isBleMode() {\r\n" +
                "        return " + appInfo.isBle() + ";\r\n" +
                "    }"
        );

        replyFileContext(
            appInfo.getHilinkDeviceJsFile(),
            appInfo.getOldPkgName(),
            appInfo.getPkgName()
        );

        // AppID
        String configJsonContext = IOUtils.readFileContent(appInfo.getConfigJsonFile());
        if (configJsonContext == null) {
            throwError(appInfo.getConfigJsonFile().getPath() + "读取失败");
        }
        EntryConfig entryConfig = JSON.parseObject(configJsonContext, EntryConfig.class);
        if (entryConfig == null || entryConfig.app == null || entryConfig.getModule() == null) {
            throwError("entry config.json文件解析失败");
        }
        String currentAppid = null;
        for (EntryConfig.Module.MetaData.Item datum : entryConfig.getModule().getMetaData().getCustomizeData()) {
            if ("com.huawei.hms.client.appid".equals(datum.getName())) {
                currentAppid = datum.getValue();
            }
        }
        if (currentAppid == null) {
            System.out.println("APPID替换失败, 请稍后手动处理或联系维护人员");
        } else {
            configJsonContext = configJsonContext.replace(currentAppid, appInfo.getAppId());
        }
        if (appInfo.getPkgName().equals(appInfo.getOldPkgName())) {
            LogUtil.error("新包名和旧包名相同, 取消后续操作");
            LogUtil.info("如仍需要修改, 请设置新的包名重新执行");
            return;
        }
        // 包名
        String bundleName = entryConfig.getApp().getBundleName();
        if (bundleName == null) {
            System.out.println("bundleName替换失败, 请稍后手动处理或联系维护人员");
        } else {
            configJsonContext = configJsonContext.replace(bundleName, appInfo.getPkgName());
        }

        IOUtils.writeContent(configJsonContext, appInfo.getConfigJsonFile(), false);

        // 目录
        // 创建新目录
        File newPkgRoot = appInfo.getNewPkgDir();
        if (newPkgRoot.exists()) {
            throwError("要创建的目录已存在" + newPkgRoot.getPath());
        }
        boolean mkdirSuccess = newPkgRoot.mkdirs();
        if (mkdirSuccess) {
            LogUtil.info("新包名目录创建成功, " + newPkgRoot.getPath());
        } else {
            throwError("新包名目录创建失败, " + newPkgRoot.getPath());
        }

        File oldCodRoot = appInfo.getApplicationJavaFile().getParentFile();
        File[] oldSrcFiles = oldCodRoot.listFiles();
        if (oldSrcFiles != null && oldSrcFiles.length != 0) {
            for (File file : oldSrcFiles) {
                if (file.isFile()) {
                    IOUtils.copyFile(file, new File(newPkgRoot, file.getName()));
                } else {
                    IOUtils.copyDir(file, new File(newPkgRoot, file.getName()));
                }
            }
        }
        LogUtil.info("复制java代码结束");

        // 修改代码中的导包
        LogUtil.info("开始修改代码中的包名");
        modifyImportPackageName(newPkgRoot, appInfo.getOldPkgName(), appInfo.getPkgName());

        int index = 0;
        // 删除旧目录
        File oldSrcRoot = new File(appInfo.getJavaSrcRoot(), subIndexValue(appInfo.getOldPkgName(), index));
        File newSrcRoot = new File(appInfo.getJavaSrcRoot(), subIndexValue(appInfo.getPkgName(), index));

        File delDir;
        while (newSrcRoot.getName().equals(oldSrcRoot.getName())) {
            index++;
            oldSrcRoot = new File(oldSrcRoot, subIndexValue(appInfo.getOldPkgName(), index));
            newSrcRoot = new File(newSrcRoot, subIndexValue(appInfo.getPkgName(), index));
        }
        delDir = oldSrcRoot;
        LogUtil.info("删除旧目录: " + delDir.getPath());
        IOUtils.deleteFileOrDir(delDir);

        // 产品名称替换
        String jsonRegex = "\"name\": \"faName\",\r?\n? +\"value\": \"[^\"]+\"";
        String kvRegex = "\"faName\": \"[^\"]+\"";
        Pattern faNameJsonPattern = Pattern.compile(jsonRegex, Pattern.DOTALL | Pattern.MULTILINE);
        Pattern faNameKvPattern = Pattern.compile(kvRegex);

        ArrayList<File> targetNameFiles = findFiles(appInfo.getJavaSrcRoot().getParentFile(), "string.json");
        targetNameFiles.addAll(findFiles(appInfo.getJavaSrcRoot().getParentFile(), "en-US.json"));
        targetNameFiles.addAll(findFiles(appInfo.getJavaSrcRoot().getParentFile(), "zh-CN.json"));
        for (File stringJsonFile : targetNameFiles) {
            String path = stringJsonFile.getPath();
            String newProductName = path.contains("en_US") || path.contains("en-US") ?
                appInfo.getProductEnName() :
                appInfo.getProductChName();
            String context = IOUtils.readFileContent(stringJsonFile);
            if (context == null) {
                LogUtil.error(path + "资源文件中FAName替换失败, 请稍后手动替换");
                return;
            }
            Matcher matcher = faNameJsonPattern.matcher(context);
            String oldValue = null;
            String newValue = null;
            if (matcher.find()) {
                oldValue = matcher.group();
                newValue = "\"name\": \"faName\",\n" +
                    "      \"value\": \"" + newProductName + "\"";
            } else {
                Matcher kvMatcher = faNameKvPattern.matcher(context);
                if (kvMatcher.find()) {
                    oldValue = kvMatcher.group();
                    newValue = "\"faName\": \"" + newProductName + "\"";
                }
            }
            if (oldValue == null) {
                LogUtil.error(path + "产品名称替换失败, 请稍后手动替换");
                return;
            }

            if (!appInfo.isFirstConfig) {
                appInfo.setFirstConfig(oldValue.contains("Hilink连接示例"));
            }

            replyFileContext(stringJsonFile, oldValue, newValue);
        }
        LogUtil.info("FA名称修改结束");
    }

    private static String subIndexValue(String pkgName, int index) {
        return pkgName.split("\\.")[index];
    }

    private static void modifyImportPackageName(File dir, String oldString, String newString) {
        File[] files = dir.listFiles();
        if (files != null && files.length != 0) {
            for (File file : files) {
                if (file.isFile()) {
                    replyFileContext(file, oldString, newString);
                } else {
                    modifyImportPackageName(file, oldString, newString);
                }
            }
        }
    }

    private static void replyFileContext(File target, String oldString, String newString) {
        String fileContent = IOUtils.readFileContent(target);
        if (fileContent != null) {
            IOUtils.writeContent(fileContent.replace(oldString, newString), target, false);
        }
    }

    private static boolean replyFileContextRegex(File target, String regex, String newString) {
        String fileContent = IOUtils.readFileContent(target);
        Pattern compile = Pattern.compile(regex);
        Matcher matcher = compile.matcher(fileContent);
        if (matcher.find()) {
            IOUtils.writeContent(fileContent.replace(matcher.group(), newString), target, false);
            return true;
        }
        return false;
    }

    @Getter
    @Setter
    public static class StringsJson {
        private ArrayList<Strings> strings;

        @Getter
        @Setter
        public static class Strings {
            private String name;
            private String value;
        }
    }

    @Getter
    @Setter
    public static class EntryConfig {
        private App app;
        private Module module;

        @Getter
        @Setter
        public static class App {
            private String bundleName;
        }

        @Getter
        @Setter
        public static class Module {
            private MetaData metaData;

            @Getter
            @Setter
            public static class MetaData {
                private List<Item> customizeData;

                @Getter
                @Setter
                public static class Item {
                    private String name;
                    private String value;
                }
            }
        }
    }

    public static void collectFile(File baseDir, ArrayList<File> allFile, String fileName) {
        File[] files = baseDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory() || pathname.getName().equals(fileName);
            }
        });
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    allFile.add(file);
                } else {
                    collectFile(file, allFile, fileName);
                }
            }
        }
    }

    private static ArrayList<File> findFiles(File baseDir, String fileName) {
        ArrayList<File> files = new ArrayList<>();
        collectFile(baseDir, files, fileName);
        return files;
    }

    private static File findFile(File baseDir, String fileName) {
        ArrayList<File> files = new ArrayList<>();
        collectFile(baseDir, files, fileName);
        return files.isEmpty() ? null : files.get(0);
    }

    @Data
    static class FAConfigInfo {
        // 项目根目录
        private String root;

        // 用户输入
        private boolean isBle;
        private String pkgName;
        private String appId;
        private String productChName;
        private String productEnName;

        // 解析到的项目属性
        private String oldPkgName;
        private File javaSrcRoot;

        private File configJsonFile;
        private File fAConfigJavaFile;
        private File ApplicationJavaFile;
        private File hilinkDeviceJsFile;
        private File libCongJsonFile;

        private boolean isFirstConfig = false;

        public void parse() {
            setJavaSrcRoot(new File(root, "\\entry\\src\\main\\java"));
            setConfigJsonFile(findFile(javaSrcRoot.getParentFile(), "config.json"));
            if (configJsonFile == null) {
                throwError("无法找到配置文件config.json");
            }
            setFAConfigJavaFile(findFile(new File(root), "FAConfig.java"));
            if (configJsonFile == null) {
                throwError("无法找到配置文件FAConfig.java");
            }
            setApplicationJavaFile(findFile(javaSrcRoot, "Application.java"));
            if (ApplicationJavaFile == null) {
                throwError("未找到Application.java文件, 无法替换Java目录");
            }
            String pkgNameRegex = "main\\.java\\.([\\w]{2,15}\\.?){2,5}\\.Application\\.java";
            Pattern pkgPattern = Pattern.compile(pkgNameRegex);
            Matcher matcher = pkgPattern.matcher(ApplicationJavaFile.getPath().replace("\\", "."));
            String oldPkgName;
            if (matcher.find()) {
                oldPkgName = matcher.group().replace("main.java.", "").replace(".Application.java", "");
                setOldPkgName(oldPkgName);
            } else {
                throwError("未匹配到有效目录, 无法替换Java目录");
            }
            setHilinkDeviceJsFile(findFile(javaSrcRoot.getParentFile(), "hilink-device.js"));
            if (hilinkDeviceJsFile == null) {
                throwError("未找到hilink-device.js");
            }
            setLibCongJsonFile(findFile(new File(root, "lib"), "config.json"));
        }

        public File getNewPkgDir() {
            return new File(javaSrcRoot, getPkgName().replace(".", "\\"));
        }
    }

    static class IOUtils {
        public static final String UTF_8 = "UTF-8";

        public static void deleteFileOrDir(File file) {
            if (file != null && file.exists()) {
                if (file.isDirectory()) {
                    File[] files = file.listFiles();
                    for (File fileItem : files) {
                        deleteFileOrDir(fileItem);
                    }
                    file.delete();
                } else {
                    file.delete();
                }
            }
        }

        public static void copyFile(File file, File newFile) throws IOException {
            BufferedInputStream in = null;
            BufferedOutputStream out = null;
            try {
                in = new BufferedInputStream(new FileInputStream(file));
                out = new BufferedOutputStream(new FileOutputStream(newFile));
                streamReadWrite(in, out);
            } finally {
                ioClose(in, out);
            }
        }

        public static void streamReadWrite(BufferedInputStream in, BufferedOutputStream out) throws IOException {
            int len = -1;
            while ((len = in.read()) != -1) {
                out.write(len);
            }
        }

        public static String getFileContentByBufferedReader(BufferedReader in) throws IOException {
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = in.readLine()) != null) {
                sb.append(line + "\r\n");
            }
            if (sb.length() == 0) {
                return null;
            }
            return sb.substring(0, sb.length() - 2);
        }


        public static String getFileContext(File file, String coding) throws IOException {
            BufferedReader in = null;
            try {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(file), coding));
                return getFileContentByBufferedReader(in);
            } finally {
                ioClose(in);
            }
        }

        public static String readFileContent(File file) {
            try {
                return getFileContext(file, "UTF-8");
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        public static void writeContent(String content, File file, boolean append) {
            try {
                writeContent(content, UTF_8, file, append);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public static synchronized void writeContent(String content, String coding, File file, boolean append) throws IOException {
            BufferedWriter out = null;
            try {
                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, append), coding));
                out.write(content);
            } finally {
                ioClose(out);
            }
        }

        public static void copyDir(File sourceDir, File targetDir) throws IOException {
            if (sourceDir.equals(targetDir)) {
                throw new RuntimeException("Same dir!");
            }
            File newDir = new File(targetDir.getAbsolutePath(), sourceDir.getName());
            if (newDir.mkdirs()) {
                File[] files = sourceDir.listFiles();
                if (files != null) {
                    for (File f : files) {
                        if (f.isDirectory()) {
                            copyDir(f, newDir);
                        } else if (f.isFile()) {
                            copyFile(f, new File(newDir, f.getName()));
                        }
                    }
                }
            } else {
                System.out.println("The fileDir has been exist");
            }
        }

        public static void ioClose(Closeable... in) {
            if (in == null)
                return;
            for (Closeable closeable : in) {
                if (closeable != null) {
                    try {
                        closeable.close();
                    } catch (IOException e) {
                        //doNothing
                    }
                }
            }
        }
    }

    static class LogUtil {
        public static void info(String msg) {
            System.out.println(msg);
        }

        public static void error(String msg) {
            System.err.println(msg);
        }
    }
}
